--- Provide Iterators for 'String' and 'JArray'
module frege.data.Iterators where

import frege.prelude.PreludeBase (StringJ)

instance ListView   StringIterator
instance ListMonoid StringIterator


{-- 
    A 'StringIterator' is a data structure that lets us 
    view a part of a 'String' as a list.
    
    With large 'String's, performing list work on 'String's can be
    expensive, because more recent JVMs implement the @substring@
    operation via copying.
    
    Construction, 'drop', 'take', 'head' and 'tail' on a 'StringIterator' are O(1) operations.
    The drawback is that it holds on to the underlying 'String'. 
    -} 
abstract data StringIterator a = SI { !string :: StringJ a, !at :: Int, !limit :: Int }
    where
        --- empty 'StringIterator'
        empty = from String.empty

        --- concatenate the strings described by two iterators
        si1 ++ si2 = from (to si1 String.++ to si2) 

        --- Construct a 'StringIterator' from a 'String'
        from str = SI str 0 str.length

        --- realize the substring indicated by this iterator
        to SI{string,at,limit}  = substr string at limit 

        --- drop at most n characters by advancing the start index.
        --- Can be abused to "go back" towards the beginning of the 'String'
        drop :: Int -> StringIterator a -> StringIterator a
        drop n si = si.{at = max 0 (min (si.at+n) si.limit)} 

        --- sets the 'length' of the projection to n or 0, if n is negative.
        take :: Int -> StringIterator a -> StringIterator a
        take n si 
            | n >= 0    = si.{limit <- min (si.at+n)}
            | otherwise = si

        --- length 
        length :: StringIterator a -> Int
        length s = s.limit - s.at

        --- is it null?
        null SI{at, limit} = at >= limit

        --- deconstruct
        uncons SI{string,at,limit}
            | at >= limit = Nothing
            | otherwise   = case SI string (at+1) limit of
                                !si -> Just (string.polymorphicElemAt  at, si)


instance ListView ArrayIterator

{-- 
    A 'ArrayIterator' is a data structure that lets us 
    view a part of a 'JArray' as a list.
    
    With large 'JArray's, doing 'toList' and iterating through
    the list can get expensive memory-wise.
    
    Construction, 'drop', 'take', 'head' and 'tail' on a 'ArrayIterator' 
    are O(1) operations with a minimal memory overhead.
    The drawback is that it holds on to the underlying 'JArray'. 
    -} 

abstract data ArrayIterator a = AI { array :: JArray a, !at :: Int, !limit :: Int }
    where
        --- create an empty ArrayIterator
        empty = AI undefined 0 0  

        --- make iterator from 'JArray'
        from arr = AI arr 0 arr.length

        --- length 
        length :: ArrayIterator a -> Int
        length s = s.limit - s.at

        --- is it null?
        null AI{at, limit} = at >= limit

        --- deconstruct
        uncons AI{array,at,limit}
            | at >= limit = Nothing
            | otherwise   = case (JArray.genericElemAt array at, AI array (at+1) limit) of
                                !result -> Just result

        --- realize the subarray indicated by this iterator
        to :: ArrayElement a => ArrayIterator a -> JArray a 
        to = arrayFromList . toList 

        --- drop at most n characters by advancing the start index.
        --- Can be abused to "go back" towards the beginning of the 'String'
        drop :: Int -> ArrayIterator a -> ArrayIterator a
        drop n si = si.{at = max 0 (min (si.at+n) si.limit)} 

        --- sets the 'length' of the projection to n or 0, if n is negative.
        take :: Int -> ArrayIterator a -> ArrayIterator a
        take n si 
            | n >= 0    = si.{limit <- min (si.at+n)}
            | otherwise = si                         
